﻿/*
 * MessageDispatcher
 * 
 * MessageDispatcher is responsible to serialize/deserialize messages and select a contract to handle the message
 * 
 * Author: snowdreamist
 * Date: 2011/11/24
 * 
 */

using System;
using System.IO;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Text;
using BitTorrentSharp.BEncoding;
using BitTorrentSharp.KRPC.Network;
using BitTorrentSharp.KRPC.Contract;
using BitTorrentSharp.KRPC.Handler;

namespace BitTorrentSharp.KRPC.Messages
{
    /// <summary>
    /// Message Dispatcher
    /// </summary>
    public sealed class DefaultMessageDispatcher : IMessageDispatcher
    {
        internal DefaultMessageDispatcher() { }

        #region Messages

        private MessageContext GetMessageContext(IPEndPoint endPoint, Transaction transaction)
        {
            throw new NotImplementedException();
        }

        #endregion

        public void OnMessageIncoming(byte[] data, IPEndPoint endPoint)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            if (endPoint == null)
                throw new ArgumentNullException("endPoint");

            BValue value;

            // deserialize to BValue
            try
            {
                using (MemoryStream memStm = new MemoryStream(data))
                    value = BValue.Deserialize(memStm);
            }
            catch (Exception error)
            {
                throw new KRPCRawSerializeException(error);
            }

            if (!(value is BDictionary))
                throw new KRPCException(string.Format("BDictionary expected, get {0}", value.GetType().Name));

            // get message
            Message msg = null;

            try
            {
                msg = Message.Deserialize(value as BDictionary);
            }
            catch (Exception error)
            {
                throw new KRPCMessageException("Deserialize message failed", error);
            }

            // dispatch message
            var result = this.Dispatch(msg);

            // send result if necessary
            if (result != null)
            {
                // send

            }
        }

        #region Dispatcher

        private List<KRPCParameter> DeserializeParameters(MessageContext context)
        {
            throw new NotImplementedException();
        }

        private byte[] Dispatch(Message message)
        {
            if (message.Type == MessageType.Query)
            {
                // This is request

                // Create new message context

                using (MessageContext context = new MessageContext())
                {

                    #region init message context
                    // basic
                    context.Host = KRPCHost.Current;
                    context.Message = message;
                    context.IsResponse = false;
                    // Select
                    context.Service = context.Host.ServiceSelector.Select(context);
                    context.Operation = context.Host.OperationSelector.Select(context);
                    // Deserialize parameter
                    context.Parameters = this.DeserializeParameters(context);

                    #endregion

                    // invoke service
                    KRPCHost.Current.Invoker.InvokeLocalMethod(context);

                    // get response
                    Message respMsg = null;

                    #region Get response

                    if (context.Error == null)
                    {
                        // success
                        BValue returnValue = null;
                        // serialize result
                        try
                        {
                            //returnValue = context.Operation.ReturnType.Serializer.Value.Serialize(context.Results, context.Operation.ReturnType, context);
                        }
                        catch (Exception error)
                        {
                            throw new KRPCMessageException("Serialize result message failed", error);
                        }

                        respMsg = new ResponseMessage(returnValue);
                    }
                    else
                    {
                        // error
                        respMsg = new ErrorMessage(context.Error, ErrorType.ServerError);
                    }

                    #endregion

                    // raw serialize response
                    BDictionary rawResult = null;

                    try
                    {
                        rawResult = respMsg.Serialize();
                    }
                    catch (Exception error)
                    {
                        throw new KRPCRawSerializeException(error);
                    }

                    // return byte[]
                    using (MemoryStream memStm = new MemoryStream())
                    {
                        rawResult.Serialize(memStm);
                        return memStm.ToArray();
                    }
                }
            }
            else
            {
                // This is a response
                using (MessageContext context = new MessageContext())
                {
                    #region init message context
                    // basic
                    context.Host = KRPCHost.Current;
                    context.Message = message;
                    context.IsResponse = true;

                    #endregion

                    // get async invoke context
                    var invokeContext = context.Host.AsyncInvokePool.AsyncInvoke(context.Message);

                    if (invokeContext == null)
                    {
                        // no invoke context, print to tracer/debugger, ignored

                    }
                    else
                    {
                        // assign message context
                        context.Operation = invokeContext.Operation;
                        context.Service = context.Operation.Service;
                        // get response object
                        if (context.Message.Type == MessageType.Error)
                        {
                            // raise an exception
                            context.Error = (context.Message as ErrorMessage).ToException();
                        }
                        else if (context.Message.Type == MessageType.Response)
                        {

                        }
                        else
                        {
                            throw new KRPCInvokeException("Invalid message type");
                        }
                        // resume
                        context.Host.Invoker.ResumeLocalMethod(context, invokeContext);
                    }
                }
            }

            throw new NotImplementedException();
        }

        #endregion

        
    }
}
